#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import struct
import base64


class simpleEncode:
    saltList = [ \
        27169, 40118, 15746, 46781, 24687, 24123, 47311, 59611, 18160, 45475, 50579, 43737, 4781, \
        25186, 20206, 17980, 45823, 59517, 52886, 18223, 40892, 39343, 44800, 63708, 29754, 35217, \
        40416, 11340, 27150, 53350, 47583, 28213, 10210, 50380, 51411, 58484, 39959, 17223, 20773, \
        35736, 10074, 30263, 32031, 61941, 58707, 34553, 39676, 31879, 35627, 22845, 14821, 39610, \
        47017, 14078, 44191, 26197, 53273, 6856, 40221, 27320, 48440, 29960, 5534, 41934, 45138, \
        32004, 20489, 929, 46244, 50484, 23904, 35713, 10679, 34919, 32634, 25969, 60846, 63039, \
        21902, 29667, 57289, 24611, 9723, 25560, 49950, 21004, 24033, 17802, 56595, 47018, 37543, \
        18319, 51295, 47608, 38813, 41381, 9342, 14593, 3305, 40749, 28682, 14670, 22975, 50454, \
        44675, 49047, 31683, 42053, 37760, 18555, 15989, 6479, 45929, 26445, 6048, 25906, 15145, \
        4550, 33112, 15218, 31667, 46126, 43391, 56453, 57349, 39364, 7036, 50021 \
        ]
    saltListLen = 128

    def __init__(self):
        pass

    def ROTATE_LEFT(self, data, bitLen, n):
        nT1 = (data << n) & 0xFF
        nT2 = data >> (bitLen - n)
        result = nT1 | nT2
        return result

    def decodeWithSeedList(self, dataList, seedList):
        result = bytearray()
        nLen = len(dataList)
        nSeed = seedList[0] * 256 + seedList[1]
        j = 0
        k = 0
        m = nSeed % self.saltListLen
        for i in range(0, nLen):
            nAscII = dataList[i]
            nSalt = self.saltList[m] & 0xFF
            n = self.saltList[m] & 0x7
            nSalt = self.ROTATE_LEFT(nSalt, 8, n)

            nXorSeed = seedList[j] ^ nSalt
            nT1 = nAscII ^ nXorSeed
            result.append(nT1)
            k += 1
            j += 1
            if j >= 2:
                j = 0
            m += 1
            if m >= self.saltListLen:
                m = 0
        return result

    def decodeWOSeed(self, bytesIn):
        seedList = bytesIn[0:2]
        dataList = bytesIn[2:]
        return self.decodeWithSeedList(dataList, seedList)


class LoRAMACCoder:
    # const definition
    LORA_CMD_NULL_CMD = 0  # 空命令,无需解释和执行
    LORA_CMD_EXTEND_CMD = 1  # 扩展命令,用于避免两个字节的命令类型不够的情况
    LORA_CMD_ACK_SUCCESS = 2  # 上次命令的应答，成功
    LORA_CMD_ACK_FAIL = 3  # 上次命令的应答，失败

    LORA_CMD_REGISTRATION_CLEANUP = 9  # 服务登记清除，所有设备需要重新登记
    LORA_CMD_REGISTRATION_REQUEST = 10  # 服务登记请求，一个Terminal向router申请加入LoRa网络,同时会上传自己的唯一号 devID(IMEI),MAID(制造商代号)和PID(项目代号)
    LORA_CMD_REGISTRATION_FEEDBACK = 11  # 服务登记回馈，一个Terminal向router申请加入LoRa网络,同时会下发内部地址
    LORA_CMD_REGISTRATION_REPORT = 12  # 服务登记状态报告，一个router向上级报告加入网络分配情况
    LORA_CMD_ADDR_REQUEST = 13  # 内部地址分配请求,一般是一个Terminal加入一个LoRa后,需要新分配或者获取已经分配的内部地址。
    LORA_CMD_ADDR_FEEDBACK = 14  # 内部地址分配回馈,返回新分配或者获取已经分配的内部地址。
    LORA_CMD_SYNC_CMD = 15  # router 同步,Terminal发送的开始时间,以及,分组和顺序
    LORA_CMD_SYNC_TIME = 16  # router 同步时间
    LORA_CMD_SYNC_RANDOM = 17  # router 给出自由time slot,等需要发送设备请求，注册等信息的设备。
    LORA_CMD_VERIFY_REQUEST = 18  # 要求验证对方设备的真伪，需要发送一组随机数,至少是8个字节的数据,
    LORA_CMD_VERIFY_FEEDBACK = 19  # 验证反馈,回送对方8个字节的数据,和对应的MD5或者其他方式的hash结果,结果不少于8个字节。
    LORA_CMD_DHCP_POOL_REQUEST = 20  # host 主设备向上级服务器申请一个内部地址池
    LORA_CMD_DHCP_POOL_FEEDBACK = 21  # 上级服务器对 host 主设备向申请一个内部地址池请求的回应,给一个段落(开始和结束)
    LORA_CMD_ADDR_CHANGE = 22  # 对特定设备内部地址变更的通知,是对LORA_CMD_ADDR_REQUEST/LORA_CMD_ADDR_FEEDBACK的增强,格式和LORA_CMD_ADDR_FEEDBACK相同
    LORA_CMD_FREQ_CHANGE = 23  # 对特定设备通信频率变更的通知,
    LORA_CMD_ADDR_REJECT = 24  # 对特定设备拒绝服务的通知,
    LORA_CMD_DATA_REPORT = 25  # 数据上报消息，或者是状态上报消息

    # application cmd list
    LORA_CMD_APP_EXTEND_CMD = 256  # 应用扩展命令集合
    LORA_CMD_APP_FMRADIO = 257  # FM Radion APPLICATION CMD
    LORA_CMD_APP_DIANJI = 258  # 电机控制应用 APPLICATION CMD

    # 公共常量定义
    LORA_MAC_ADDRESS_LEN = 4
    headerLength = 12
    len_stDataCMD_00 = 14
    soureAddrPosition = 0
    destAddrPosition = 4
    seqPosition = 8
    fillPosition = 9
    CMDPosition = 10

    # FM Radio 部分常量定义
    # 频率2=byte，开关(1bit)+设备有效(1bit)+校园(1bit)+音量(5bit)=1 byte,背景灯 1 byte,静噪 1bit+ 阈值+5bit =1 byte
    len_APP_stFMCommand = 4
    len_APP_stFMStat = 8
    CONST_APP_FM_STAT_STRING_LENGTH = 6
    CONST_APP_FM_STAT_STRING_SWITCH_MASK = 0x80
    CONST_APP_FM_STAT_STRING_VALID_MASK = 0x40
    CONST_APP_FM_STAT_STRING_CAMPUS_MASK = 0x20
    CONST_APP_FM_STAT_STRING_VOL_MASK = 0x1F
    CONST_APP_FM_STAT_STRING_DSP_MASK = 0x80
    CONST_APP_FM_STAT_STRING_SN_MASK = 0x1F

    FM_SET_FRE = 0
    FM_FRE_DOWN = 1
    FM_FRE_UP = 2
    FM_PLAY_PAUSE = 3
    FM_SET_VOL = 4
    FM_VOL_DOWN = 5
    FM_VOL_UP = 6
    FM_SET_BANK = 7
    FM_SET_CAMPUS = 8
    FM_SET_DSP = 9
    FM_SET_SN_THR = 10
    FM_RESET = 11
    FM_STATUS = 12
    FM_SCAN = 13
    FM_SCAND = 14
    FM_SCANU = 15
    FM_SCAMSTOP = 16
    FM_CHNUM = 17
    FM_CHDOWN = 18
    FM_CHUP = 19
    FM_SET_ALL = 20
    FM_STAT_REPORT = 21
    FM_SAVE_FREQ = 22
    FM_OP_SAVE_REEQ = 23

    APP_FM_command_Position = 0
    APP_FM_flag_Position = 1
    APP_FM_param_Position = 2
    APP_FM_statString_Position = 2

    # C struct format string
    fmt_stDataCMD_00 = "BBBBBBBBBBhh"
    fmt_stDataCMD_RegistrationRequest = "BBBBBBBBBBhLHHBBBBBBBB"
    fmt_stDataCMD_verifyFeedbackCMD = "BBBBBBBBBBhLHHBBBBBBBB"
    fmt_stDataCMD_synCMD = "BBBBBBBBBBhBBBBBBBBBBBB"
    fmt_stDataCMD_stDataCMD_APP = "BBBBBBBBBBhBBBBBBBBBBBBBBBB"
    fmt_stDataCMD_frequencyChange = "BBBBBBBBBBhh"

    # FM Radio data
    fmt_APP_stFMCommand = "BBh"
    fmt_APP_stFMStat = "BBBBBBBB"

    # others
    headerSet = {}

    def decode_header(self, dataIn):
        result = {"CMD": 0, "sourceAddr": "", "destAddr": "", "seq": "", "fill": ""}
        tempData = dataIn[0:self.len_stDataCMD_00]
        set1 = struct.unpack(self.fmt_stDataCMD_00, tempData)

        cmd = set1[self.CMDPosition]
        result["CMD"] = str(cmd)

        aList = []
        for i in range(self.soureAddrPosition, self.soureAddrPosition + self.LORA_MAC_ADDRESS_LEN):
            aList.append(str(set1[i]))
            aList.append(".")
        aList.pop()
        result["sourceAddr"] = "".join(aList)

        aList = []
        for i in range(self.destAddrPosition, self.destAddrPosition + self.LORA_MAC_ADDRESS_LEN):
            aList.append(str(set1[i]))
            aList.append(".")
        aList.pop()
        result["destAddr"] = "".join(aList)

        result["seq"] = str(set1[self.seqPosition])
        result["fill"] = str(set1[self.fillPosition])
        return result

    def decode(self, dataIn):
        self.headerSet = self.decode_header(dataIn)
        cmd = int(self.headerSet["CMD"])
        appSet = {}

        if cmd == self.LORA_CMD_NULL_CMD:
            pass
        elif cmd == self.LORA_CMD_EXTEND_CMD:
            self.func_cmd_extend_cmd(dataIn);
        elif cmd == self.LORA_CMD_ACK_SUCCESS:
            pass
        elif cmd == self.LORA_CMD_ACK_FAIL:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_CLEANUP:
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REQUEST:
            appSet = self.func_cmd_registration_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_FEEDBACK:
            appSet = self.func_cmd_registration_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_REGISTRATION_REPORT:
            appSet = self.func_cmd_registration_report(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_REQUEST:
            appSet = self.func_cmd_addr_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_FEEDBACK:
            appSet = self.func_cmd_addr_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_SYNC_CMD:
            pass
        elif cmd == self.LORA_CMD_SYNC_TIME:
            pass
        elif cmd == self.LORA_CMD_SYNC_RANDOM:
            pass
        elif cmd == self.LORA_CMD_VERIFY_REQUEST:
            appSet = self.func_cmd_verify_request(dataIn)
            pass
        elif cmd == self.LORA_CMD_VERIFY_FEEDBACK:
            appSet = self.func_cmd_verify_feedback(dataIn)
            pass
        elif cmd == self.LORA_CMD_ADDR_CHANGE:
            pass
        elif cmd == self.LORA_CMD_DATA_REPORT:
            pass
        elif cmd == self.LORA_CMD_APP_EXTEND_CMD:
            pass
        elif cmd == self.LORA_CMD_APP_FMRADIO:
            appSet = self.func_cmd_app_fmradio(dataIn)
            pass
        elif cmd == self.LORA_CMD_APP_DIANJI:
            pass
        result = dict(self.headerSet, **appSet)
        return result

    def func_cmd_extend_cmd(self, dataIn):
        result = {}
        return result

    def func_cmd_registration_request(self, dataIn):
        result = {}
        return result

    def func_cmd_registration_feedback(self, dataIn):
        result = {}
        return result

    def func_cmd_registration_report(self, dataIn):
        result = {}
        return result

    def func_cmd_addr_request(self, dataIn):
        result = {}
        return result

    def func_cmd_addr_feedback(self, dataIn):
        result = {}
        return result

    def func_cmd_verify_request(self, dataIn):
        result = {}
        return result

    def func_cmd_verify_feedback(self, dataIn):
        result = {}
        return result

    def func_cmd_app_fmradio(self, dataIn):
        result = {}
        appData = dataIn[self.headerLength:]
        tempData = appData[0:self.len_APP_stFMCommand]
        set1 = struct.unpack(self.fmt_APP_stFMCommand, tempData)
        command = set1[self.APP_FM_command_Position]
        if command == self.FM_STAT_REPORT:
            tempData = appData[0:self.len_APP_stFMStat]
            set1 = struct.unpack(self.fmt_APP_stFMStat, tempData)
            flag = set1[self.APP_FM_flag_Position]
            statString = set1[self.APP_FM_statString_Position:]
            #
            frequency = statString[1] << 8
            frequency += statString[0]
            nT1 = statString[2]
            stat = (nT1 & self.CONST_APP_FM_STAT_STRING_SWITCH_MASK) >> 7
            err = (nT1 & self.CONST_APP_FM_STAT_STRING_VALID_MASK) >> 6
            campus = (nT1 & self.CONST_APP_FM_STAT_STRING_CAMPUS_MASK) >> 5
            vol = nT1 & self.CONST_APP_FM_STAT_STRING_VOL_MASK
            blk = statString[3]
            nT1 = statString[4]
            dsp = (nT1 & self.CONST_APP_FM_STAT_STRING_DSP_MASK) >> 7
            snthr = nT1 & self.CONST_APP_FM_STAT_STRING_SN_MASK

            result["appCMD"] = str(command)
            result["STAT"] = str(stat)
            result["freq"] = str(frequency)
            result["err"] = str(err)
            result["campus"] = str(campus)
            result["vol"] = str(vol)
            result["BLK"] = str(blk)
            result["DSP"] = str(dsp)
            result["SN"] = str(snthr)

            pass
        else:
            pass
        pass
        return result


def to_decode_sub_data(strIn):
    result = {}
    coder = simpleEncode()
    bytes = bytearray()
    strOut = base64.b64decode(strIn)
    bytes.extend(strOut)
    byteResult = coder.decodeWOSeed(bytes)
    macCoder = LoRAMACCoder()
    result = macCoder.decode(byteResult)
    return result


def to_decode_data(msg):
    result = {}
    aSet = json.loads(msg)
    aList = aSet["data"]

    # print aList
    newList = []
    for data in aList:
        set1 = to_decode_sub_data(data)
        newList.append(set1)
    nLen = len(newList)
    result["pushID"] = aSet["pushID"]
    result["len"] = str(nLen)
    result["data"] = newList
    return result


def main():
    msg = '{"data":["SmMv49m5D9eBfNHjkMz4sWfADTvxO4P+wdz6wwak","6BjAkwjZqykHv1HUd152u8iDDoltpnLrJ9uxv1Ty","aLT7o6cbmtTLDWG8zgY09P70rf0Ct7MHgVbDK8ur","XbTOo5Ibr9T+DVW8+wYB9Mv0mP03t4YHtFb2K/6r","3Xy9mBrVX3nXykWlO6M4YcXM5/9svyLTeOsWq92x","BI8H2KsvU+zTjudnJm7VDBljLQ7CEUMEpyuc5p8A","jOKf76R0k8+QKL9KDTg3RdpIoyDUU9QrEWkzUiyu","bUpEAMbQDv3+Up6S64u7YHHjMYfIst7+8ki+70XZ"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"47382","seed2":"9a5e6f5b","DateTime":"20170509042754"}'
    msg = '{"data":["XM4/ec/SQ77bDxHkxWWaCXc1YHDJzI9rdF2DS0AF","Kfh025ZNSRzuVeZVIk6LIUolVu+/S5xxkjvWV4xv","xkrvAG3Qpf1VUpWSQIsQYNrjmodjsnX+WUgV7+7Z","TZ5ikMZXE3HGPGIGJQTXIFNuDVceOfF0HiGCNH9W","Hzp8Kic3eCFWe4d5VHhqOUCKedK+pbwljCPA4pj6","OEaY7ePwEQyT2BJZql7ynjuFdGaq7OuBl76L8qdE","DEl/CO7nU0IT252W1c/ZiqLSKZDB5vT6uNYOmqlh","ZSPiAMaCPeL+rVy5l+2zekacAHrQ7M8RrYDdG22E"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"19816","seed2":"c737e5ac","DateTime":"20170509044637"}'
    msg = '{"data":["LALmHZveC3TcHraxmIqCwVeuBp/t1SzPxCHMxq+b","MDMayI/5Wqn4kY0LOZWWevQi/HN5UzPog9rSmK+Q","Z1kNIqDzX0LTx2GKw3DhhmZGI03F2bz3wIvO+a8L","oDHHJUpn31YKAmuSGQi9lpd7bynpe+NRo+oT2EKa","c9zcJMBoEyFfhllPplkigsN0+wffew9nmAZrDH8U","Tr2Gsv3xzgcN9gzdTGfoVCgVXhRbLldiljqOfmQl","EauvMeJkLQ1J6Pv4pGT2DHcIoZMbDAzwGqsYgAbK","N+5Z4eVCV45lXuAnq2SdXhOhf0h/Qz1ZgDfQMMfz"],"pushID":"192.168.0.1","SVER":1,"SCRTY":1,"COEXIST":1,"seed1":"28817","seed2":"54783074","DateTime":"20170509044756"}'
    print to_decode_data(msg)


if __name__ == "__main__":
    main()
